/**
 * MedisStream Recording API 是专为录制媒体流定义的
 * 
 *    这个 API 试图让录制变得简单，同时也允许进行一些复杂操作。在最简单的场景下，应用程序实例化一个 MediaRecorder 对象，
 * 调用 start() 方法，然后再 调用 stop() 方法或者等待 MediaStreamTrack 结束。录制内容会通过 MediasRecorder 对象的 ondataavailable 事件抛出，
 * 这些数据默认经过平台编码。可以使用函数查询平台的可用编码集，并选择希望的那一个。应用程序也可以选择一次抛出的数据量。
 * 默认情况下，当录制完成时会得到一个完整的 Blob 对象。然而，应用程序还可以选择定期接收较小的数据缓冲
 */

/**
 * Media Recorder API 定义了 MediaRecorder 接口，通过该接口实例化的对象处理录制行为
 *    Constructor (MediaStrean stream[, MediaRecorderOptions options]) :{
 *        stream: 被录制的媒体流对象，会作为实例对象的 stream 属性的的值
 *        options: 可选的，用于指示如何参与录制的 UA 选项字典
 *    }
 * 遇到的独立接口或枚举类型
 *    MediaRecorderOptions: {
 *        mimeType: {string}，用于录制的容器和编解码器格式，可带有所有受支持的参数。如果 UA 不支持该类型或者参数则要抛出一个 NotSupportedError 异常。如果这个参数没有被设置，UA 会使用默认值。这个容器格式，不论是外部传入还是默认值，都被用作 mimeType 属性值
 *        audioBitsPerSecond: {unsigned long}，编码音频轨道时每秒总计编码的位数
 *        videoBitsPerSecond: {unsigned long}，编码视频轨道时每秒总计编码的位数
 *        bitsPerSecond: {unsigned long}，编码当前音视频轨道时每秒总计编码的位数，使用这个参数时，会重写覆盖掉上面两个
 *    }
 * 
 * 
 * MediaRecorder 实例字典：{
 *    stream: {MeidaStream}，被录制的 MediaStream 对象，来自于构造函数的一参
 *    mimeType: {string}，当前选择的用于录制的容器 MIME 类型。此项包括基本 mimetype 的所有参数。UA 应该支持回放任何用于录制的 mimeType 类型
 *    state: {RecordingState}，只读，当前实例对象的状态值。当实例被创建时，UA 必须将该属性设置为 inactive
 *    onstart: {EventHandler}，处理 start 事件
 *    onstop: {EventHandler}，处理 stop 事件
 *    ondataavailable； {EventHandler}，处理 dataavailable 事件，可以通过事件对象的 data 属性访问录制的 Blob 类型的数据
 *    onpause: {EventHandler}，处理 pause 事件
 *    onresume: {EventHandler}，处理 resume 事件
 *    onerror: {EventHandler}，处理异常
 *    videoBitsPerSecond: {unsigned long}，只读，编码视频轨道时每秒总计编码的位数
 *    audioBitsPerSecond: {unsigned long}，只读，编码音频轨道时每秒总计编码的位数
 *    start: {void Function([ long timeslice])}，timeslice 是返回的单一 Blob 数据的最小毫秒数。启动录制，设置 state 属性为 recording，抛出 start 事件。值得注意的是，undefined 作为 timeslice 的值，会被当做最大 long 类型值。
 *    stop: {void FUnction()}，必须在非 inactive 状态下，调用后停止修改状态，停止聚集数据，并抛出 dataavailable 事件，之后再抛出 stop 事件
 *    pause: {void Function()}，必须在非 inactive 状态下，将状态改为 paused，停止向 blob 中聚集数据，但保持实例对象有效，以便恢复。
 *    resume: {void Function()}，必须在非 inactive 状态下，将状态改为 recording，恢复向 blob 中聚集数据，抛出 resume 事件
 *    requestData: {void Function()}，必须在非 inactive 状态下，将截至调用时所有聚集的数据给到一个 blob 对象中，并抛出携带该 blob 的 dataavailable 事件。（如果还没有数据被聚集，则 blob 为空）
 *    static isTypeSupported: {boolean Function(string type)}，检查当前 UA 下，MediaRecorder 是否支持录制自动 mime 类型。若返回 true，说明这个 MediaRecorder 的实现有能力录制指定类型的 Blob 对象，如果没有足够的可用资源来支持具体的媒体编码，录制仍可能失败。
 * }
 * 遇到的独立接口或枚举类型
 *    enum RecordingState {
 *        inactive: 录制未发生，有两种可能，一是还未开始，二是已经结束
 *        recording: 录制已经开始，UA 正在捕获数据
 *        paused: 录制已经开始，之后暂停了，且任未恢复
 *    }
 */

var mimeTypes = ["video/x-sgi-movie", "video/x-msvideo", "video/x-ms-wvx", "video/x-ms-wmx", "video/x-ms-wmv", "video/x-ms-wm", "video/x-ms-asf", "video/x-m4v", "video/x-flv", "video/x-fli", "video/x-f4v", "video/webm", "video/vnd.vivo", "video/vnd.uvvu.mp4", "video/vnd.ms-playready.media.pyv", "video/vnd.mpegurl", "video/vnd.fvt", "video/vnd.dece.video", "video/vnd.dece.sd", "video/vnd.dece.pd", "video/vnd.dece.mobile", "video/vnd.dece.hd", "video/quicktime", "video/ogg", "video/mpeg", "video/mp4", "video/mj2", "video/jpm", "video/jpeg", "video/h264", "video/h263", "video/h261", "video/3gpp2", "video/3gpp", "audio/x-wav", "audio/x-pn-realaudio-plugin", "audio/x-pn-realaudio", "audio/x-ms-wma", "audio/x-ms-wax", "audio/x-mpegurl", "audio/x-aiff", "audio/x-aac", "audio/webm", "audio/vnd.rip", "audio/vnd.nuera.ecelp9600", "audio/vnd.nuera.ecelp7470", "audio/vnd.nuera.ecelp4800", "audio/vnd.ms-playready.media.pya", "audio/vnd.lucent.voice", "audio/vnd.dts.hd", "audio/vnd.dts", "audio/vnd.dra", "audio/vnd.digital-winds", "audio/vnd.dece.audio", "audio/ogg", "audio/mpeg", "audio/mp4", "audio/midi", "audio/basic", "audio/adpcm"];

/**
 * 这个实例检查了当前实现是否支持几个常用的 codec/container 组合
 */
const checkForMediaRecorderAndContentTypes = () => {
  if(window.MediaRecorder) {
    let contentTypes = [
      'video/webm',
      'video/webm;codecs=vp8',
      'video/x-matroska;codecs=avc1',
      'audio/webm',
      'video/mp4;codecs=avc1',
      'video/mp4',
      'video/invalid',
      'video/x-flv',
      'application/x-mpegURL',
      'video/MP2T',
      'video/3gpp',
      'video/quicktime',
      'video/x-msvideo',
      'video/x-ms-wmv'
    ];

    mimeTypes.forEach(mimeType => {
      console.log(`${MediaRecorder.isTypeSupported(mimeType) ? '支持' : '不支持'} ${mimeType}`);
    })
    contentTypes.forEach(contentType => {
      console.log(`${MediaRecorder.isTypeSupported(contentType) ? '支持' : '不支持'} ${contentType}`);
    })
  } else {
    alert('您的浏览器暂不支持 MediaRecorder API');
  }
}

/**
 * 录制一个音视频媒体流
 */
const recordingWebcamVideoAndAudio = () => {
  let recordedChunks = [];

  const gotMedia = (stream) => {
    let video = document.getElementById('curVideo');
    video.srcObject = stream;

    let recorder = null;
    try {
      recorder = new MediaRecorder(stream, {
        mimeType: 'video/webm'
      });
    } catch (e) {
      alert(`Exception while creating MediaRecorder: ${e.message}`);
      console.error(`Exception while creating MediaRecorder: ${e.message}`);
      return;
    }

    // 获取数据块
    const handlerDataAvailable = (event) => {
      console.log(` Recorded chunk of size: ${event.data.size}B`);
      recordedChunks.push(event.data);
    }

    // 录制结束事件处理
    const handlerStop = () => {
      console.log(recordedChunks);
      if(recordedChunks.length <= 0) {
        alert('没找到录制数据');
        return;
      }
      let depth = recordedChunks.length - 1, curLayer = 0, totalLength = 0, bufferList = [];

      // 递归体
      const recursionBody = () => {
        if(curLayer <= depth) {
          let curBlob = recordedChunks[curLayer];
          let reader = new FileReader();
          reader.readAsArrayBuffer(curBlob);
          reader.onload = () => {
            let curArrayBuffer = reader.result;
            totalLength += curArrayBuffer.byteLength;
            bufferList.push(curArrayBuffer);
            curLayer++;
            recursionBody();
          }
        } else { // 递归完成，最后一次执行此处
          // 动态追加
          let reusltArrayBuffer = new ArrayBuffer(totalLength);
          let offset = 0;
          for(let curAb of bufferList) {
            for(let i = 0; i < curAb.byteLength; i += 1) {
              reusltArrayBuffer[offset + i] = curAb[i];
            }
            offset += curAb.byteLength;
          }
          let newBlob = new Blob([reusltArrayBuffer]);
          document.querySelector('#playVideo').src = URL.createObjectURL(newBlob);
          recordedChunks = [];
          reusltArrayBuffer = null;
        }
      }

      // recursionBody();
      console.log(recordedChunks[1][1]); // 录制不出来...
      document.querySelector('#playVideo').src = URL.createObjectURL(recordedChunks[1]);
    }

    // 事件绑定
    recorder.addEventListener('dataavailable', handlerDataAvailable, false);
    recorder.addEventListener('stop', handlerStop, false);
    document.querySelector("#start").addEventListener('click', () => {
      recorder.start(1000);
    });
    document.querySelector("#end").addEventListener('click', () => {
      recorder.stop();
    });
    document.querySelector("#pause").addEventListener('click', () => {
      recorder.pause();
    });
    document.querySelector("#resume").addEventListener('click', () => {
      recorder.resume();
    })
  }

  navigator.mediaDevices.getUserMedia({
    video: true, audio: true
  }).then(gotMedia)
    .catch(e => {
      alert(`getUserMedia() failed: ${e.message}`);
      console.error(`getUserMedia() failed: ${e.message}`)
    })
}

checkForMediaRecorderAndContentTypes();
recordingWebcamVideoAndAudio();